using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Evolution.Engine.Interfaces;
using Evolution.Engine.Managers;

namespace Evolution.Engine.Objects
{
    public class Water : BaseObject, IRenderable, ILoadable
    {
        public struct VertexMultitextured
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector4 TextureCoordinate;
            public Vector4 TexWeights;

            public static int SizeInBytes = (3 + 3 + 4 + 4) * 4;
            public static VertexElement[] VertexElements = new VertexElement[]
             {
                 new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                 new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                 new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
                 new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
             };
        }

        private VertexBuffer _Vb;
        private IndexBuffer _Ib;
        VertexMultitextured[] _Vertices;
        private short _Height = 256;
        private short _Width = 256;

        /// <summary>
        /// Default 128
        /// </summary>
        public short Height { get { return _Height; } set { _Height = value; } }
        /// <summary>
        /// Default 128
        /// </summary>
        public short Width { get { return _Width; } set { _Width = value; } }

        private string _EnvironmentAsset;
        private string _BumpMapAsset;
        private TextureCube _Environment;
        private Texture2D _2DEnvironment;
        private Texture2D _BumpMap;
        private Vector4 _DeepWater = new Vector4(0, 0, 1, 1);
        private Vector4 _ShallowWater = new Vector4(0, 5, 5, 1);
        private Vector4 _Reflection = new Vector4(1, 1, 1, 1);

        private float _BumpHeight = 0.10f;
        private float _HDRMult = 3.0f;
        private float _ReflectionAmt = 1.0f;
        private float _WaterColorAmount = 1.0f;
        private float _WaveAmplitude = 1.0f;
        private float _WaveFrequency = 0.1f;

        /// <summary>
        /// Min 0, Max 2.0
        /// </summary>
        public float BumpMapHeight { get { return _BumpHeight; } set { _BumpHeight = value; } }
        /// <summary>
        /// Min 0, Max 100
        /// </summary>
        public float HDRMultiplier { get { return _HDRMult; } set { _HDRMult = value; } }
        /// <summary>
        /// Min 0, Max 2.0
        /// </summary>
        public float ReflectionAmount { get { return _ReflectionAmt; } set { _ReflectionAmt = value; } }
        /// <summary>
        /// Min 0, Max 2.0
        /// </summary>
        public float WaterColorAmount { get { return _WaterColorAmount; } set { _WaterColorAmount = value; } }
        /// <summary>
        /// Min 0, Max 10.0
        /// </summary>
        public float WaveAmplitude { get { return _WaveAmplitude; } set { _WaveAmplitude = value; } }
        /// <summary>
        /// Min 0, Max 1.0
        /// </summary>
        public float WaveFrequency { get { return _WaveFrequency; } set { _WaveFrequency = value; } }

        private float _Tick = 0.0f;
        private float _AnimSpeed = 0.0f;
        private Color _AlphaBlendColor;

        public Color AlphaBlendColor
        {
            get { return _AlphaBlendColor; }
            set
            {
                _AlphaBlendColor = value;
                if (value == Color.Black)
                    _AlphaCheck = false;
                else
                    _AlphaCheck = true;
            }
        }

        /// <summary>
        /// Min 0, Max 0.1
        /// </summary>
        public float AnimationSpeed { get { return _AnimSpeed; } set { _AnimSpeed = value; } }
        public Vector4 DeepWaterColor { get { return _DeepWater; } set { _DeepWater = value; } }
        public Vector4 ShallowWaterColor { get { return _ShallowWater; } set { _ShallowWater = value; } }
        public Vector4 ReflectionColor { get { return _Reflection; } set { _Reflection = value; } }
        /*public new Vector3 Position
        {
            get
            {
                return new Vector3(-_Position.X + (_Width / 2), -_Position.Y, -_Position.Z - (_Height / 2)); ;
            }
            set
            {
                _Position = new Vector3(-value.X - (_Width / 2), -value.Y, -value.Z + (_Height / 2));
            }
        }*/

        public Water(string environmentMap, string bumpMap, string name)
            : base(name)
        {
            _EnvironmentAsset = environmentMap;
            _BumpMapAsset = bumpMap;
            _Width = 128;
            _Height = 128;
        }

        public override void Update(GameTime gameTime)
        {
            TextManager.Instance.Write("Default", "Water : " + _Position.ToString() + " " + _Width.ToString() + ":" + _Height.ToString(), new Vector2(10, 55), Color.White);
        }

        public override void LoadContent()
        {
            // Textures
            try
            {
                _Environment = ResourceManager.Instance.LoadContent<TextureCube>(_EnvironmentAsset);
            }
            catch
            {
                _2DEnvironment = ResourceManager.Instance.LoadContent<Texture2D>(_EnvironmentAsset);
            }

            _BumpMap = ResourceManager.Instance.LoadContent<Texture2D>(_BumpMapAsset);

            // Vertices
            _Vertices = new VertexMultitextured[_Width * _Height];

            for (int x = 0; x < _Width; x++)
            {
                for (int y = 0; y < _Height; y++)
                {
                    _Vertices[x + y * _Width].Position = new Vector3(x, 0, y);
                    _Vertices[x + y * _Width].Normal = Vector3.Up;
                    _Vertices[x + y * _Width].TextureCoordinate.X = (float)x / EnvironmentManager.Instance.BlockSize;
                    _Vertices[x + y * _Width].TextureCoordinate.Y = (float)y / EnvironmentManager.Instance.BlockSize;
                }
            }

            _Vb = new VertexBuffer(GameEngine.Instance.Device, VertexMultitextured.SizeInBytes * _Width * _Height, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
            _Vb.SetData(_Vertices);

            //Index
            short[] terrainIndices = new short[(_Width - 1) * (_Height - 1) * 6];
            for (short x = 0; x < _Width - 1; x++)
            {
                for (short y = 0; y < _Height - 1; y++)
                {
                    terrainIndices[(x + y * (_Width - 1)) * 6] = (short)((x + 1) + (y + 1) * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 1] = (short)((x + 1) + y * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 2] = (short)(x + y * _Width);

                    terrainIndices[(x + y * (_Width - 1)) * 6 + 3] = (short)((x + 1) + (y + 1) * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 4] = (short)(x + y * _Width);
                    terrainIndices[(x + y * (_Width - 1)) * 6 + 5] = (short)(x + (y + 1) * _Width);
                }
            }

            _Ib = new IndexBuffer(GameEngine.Instance.Device, typeof(short), (_Width - 1) * (_Height - 1) * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
            _Ib.SetData(terrainIndices);
        }

        public void Render(GameTime gameTime)
        {
            Effect effect = ShaderManager.Instance.GetShader(_Shader).Effect;

            effect.Parameters["normalMap"].SetValue(_BumpMap);
            
            if (_Environment != null)
                effect.Parameters["cubeMap"].SetValue(_Environment);
            else
                effect.Parameters["cubeMap"].SetValue(_2DEnvironment);

            effect.Parameters["deepColor"].SetValue(_DeepWater);
            effect.Parameters["shallowColor"].SetValue(_ShallowWater);
            effect.Parameters["reflectionColor"].SetValue(_Reflection);
            effect.Parameters["time"].SetValue(_Tick += _AnimSpeed);

            effect.Parameters["bumpHeight"].SetValue(_BumpHeight);
            effect.Parameters["hdrMultiplier"].SetValue(_HDRMult);
            effect.Parameters["reflectionAmount"].SetValue(_ReflectionAmt);
            effect.Parameters["waterAmount"].SetValue(_WaterColorAmount);
            effect.Parameters["waveAmp"].SetValue(_WaveAmplitude);
            effect.Parameters["waveFreq"].SetValue(_WaveFrequency);

            bool alphaTest = GameEngine.Instance.Device.RenderState.AlphaTestEnable;
            bool alphaBlend = GameEngine.Instance.Device.RenderState.AlphaBlendEnable;
            CompareFunction alphaFunc = GameEngine.Instance.Device.RenderState.AlphaFunction;
            Blend sourceBlend = GameEngine.Instance.Device.RenderState.SourceBlend;
            Blend destinationBlend = GameEngine.Instance.Device.RenderState.DestinationBlend;
            Color blendFator = GameEngine.Instance.Device.RenderState.BlendFactor;

            if (_AlphaCheck)
            {
                if (GameEngine.Instance.Device.RenderState.AlphaTestEnable != true)
                    GameEngine.Instance.Device.RenderState.AlphaTestEnable = true;
                if (GameEngine.Instance.Device.RenderState.AlphaBlendEnable != true)
                    GameEngine.Instance.Device.RenderState.AlphaBlendEnable = true;
                if (GameEngine.Instance.Device.RenderState.AlphaFunction != CompareFunction.NotEqual)
                    GameEngine.Instance.Device.RenderState.AlphaFunction = CompareFunction.NotEqual;

                if (GameEngine.Instance.Device.RenderState.SourceBlend != Blend.BlendFactor)
                    GameEngine.Instance.Device.RenderState.SourceBlend = Blend.BlendFactor;
                if (GameEngine.Instance.Device.RenderState.DestinationBlend != Blend.One)
                    GameEngine.Instance.Device.RenderState.DestinationBlend = Blend.One;
                if (GameEngine.Instance.Device.RenderState.BlendFactor != _AlphaBlendColor)
                    GameEngine.Instance.Device.RenderState.BlendFactor = _AlphaBlendColor;

            }

            GameEngine.Instance.Device.Vertices[0].SetSource(_Vb, 0, VertexMultitextured.SizeInBytes);
            GameEngine.Instance.Device.Indices = _Ib;
            GameEngine.Instance.Device.VertexDeclaration = new VertexDeclaration(GameEngine.Instance.Device, VertexMultitextured.VertexElements);
            GameEngine.Instance.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _Width * _Height, 0, (_Width - 1) * (_Height - 1) * 2);

            if (_AlphaCheck)
            {
                if (GameEngine.Instance.Device.RenderState.AlphaTestEnable != alphaTest)
                    GameEngine.Instance.Device.RenderState.AlphaTestEnable = alphaTest;
                if (GameEngine.Instance.Device.RenderState.AlphaBlendEnable != alphaBlend)
                    GameEngine.Instance.Device.RenderState.AlphaBlendEnable = alphaBlend;
                if (GameEngine.Instance.Device.RenderState.AlphaFunction != alphaFunc)
                    GameEngine.Instance.Device.RenderState.AlphaFunction = alphaFunc;

                if (GameEngine.Instance.Device.RenderState.SourceBlend != sourceBlend)
                    GameEngine.Instance.Device.RenderState.SourceBlend = sourceBlend;
                if (GameEngine.Instance.Device.RenderState.DestinationBlend != destinationBlend)
                    GameEngine.Instance.Device.RenderState.DestinationBlend = destinationBlend;
                if (GameEngine.Instance.Device.RenderState.BlendFactor != blendFator)
                    GameEngine.Instance.Device.RenderState.BlendFactor = blendFator;
            }
        }
    }
}
